package net.avcompris.binding.helper;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

import net.avcompris.binding.Binding;
import net.avcompris.binding.impl.AbstractBinderInvocationHandler;

import com.avcompris.util.AbstractUtils;

/**
 * Helpers to rebind nodes to other Java interfaces.
 * 
 * @author David Andrianavalontsalama
 */
public abstract class BinderUtils extends AbstractUtils {

	/**
	 * rebind an existing proxy to a new Java interface. This allows to use new
	 * XPath expressions for nodes already attached to a proxy.
	 */
	public static <T> T rebind(final Object proxy, final Class<T> clazz) {

		nonNullArgument(proxy, "proxy");
		nonNullArgument(clazz, "clazz");

		if (clazz.isInstance(proxy)) {

			return clazz.cast(proxy);
		}

		if (Binding.class.isInstance(proxy)) {

			return ((Binding<?>) proxy).self(clazz);
		}

		if (Proxy.class.isInstance(proxy)) {

			final InvocationHandler invocationHandler = Proxy
					.getInvocationHandler(proxy);
			/*
			 * if (Binding.class.isInstance(invocationHandler)) {
			 * 
			 * return ((Binding<?>) invocationHandler).self(clazz); }
			 */

			if (AbstractBinderInvocationHandler.class
					.isInstance(invocationHandler)) {

				return ((AbstractBinderInvocationHandler<?>) invocationHandler)
						.rebind(clazz);
			}
		}

		throw new IllegalArgumentException(
				"Parameter appears not to be an object bound to a node.");
		// "proxy should be an instance of AbstractBinderInvocationHandler");
	}

	public static <T> T detach(final T object) {

		nonNullArgument(object, "object");

		if (!Proxy.isProxyClass(object.getClass())) {

			// Nothing to detach
			
			return object;
		}
		
		final InvocationHandler invocationHandler = Proxy
				.getInvocationHandler(object);

		if (invocationHandler == null
				|| !AbstractBinderInvocationHandler.class
						.isInstance(invocationHandler)) {

			// Don’t know how to detach
			
			return object;
		}

		@SuppressWarnings("unchecked")
		final AbstractBinderInvocationHandler<T> binderInvocationHandler = (AbstractBinderInvocationHandler<T>) invocationHandler;

		return binderInvocationHandler.detach(object);
	}
}
